Golang 实战:开发基于 GraphQL 的接口服务

Golang以其高效、稳定、简单吸引了大量开发者使用,越来越多公司和云计算平台开始选择Golang作为后端服务开发语言。
Golang对比目前主流的后端开发语言Java具有以下优势:

  • 性能好:编译为机器码的静态类型语言,能以更少的资源提供同样量级的访问量,节省云服务开支;
  • 上手快:语法更简洁上手快,标准库完善设计优秀,自带垃圾回收,开发效率高;
  • 并发友好:语言层面支持并发,可以充分利用多核,轻松应对高并发服务。

本文将以打造一个电影网站的后端服务为例,一步步教你如何用Golang开发GraphQL服务,内容涵盖:

  • 流行Golang HTTP框架对比与Echo框架教程;
  • 流行Golang GraphQL框架对比与graphql-go框架教程;
  • 使用Docker部署Golang应用;

搭建出的服务整体架构如图:
服务整体架构

HTTP框架

Golang标准库内置的net/http包能快速实现一个HTTP服务器:

1
2
3
4
5
6
7
8
9
10
11
import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintf(writer, "Hello, World!")
})
http.ListenAndServe(":8080", nil) // HTTP服务监听在8080端口
}

但其功能太基础,要用在实际项目中还需要自己补充大量常用基础功能,例如:

  • 路由参数:从URL /movie/:id 中提取id参数;
  • 静态资源托管:暴露 /static 目录下的所有文件;
  • 响应格式:返回HTML、JSON、XML等格式响应,需要设置对应HTTP响应头;
  • 日志:记录请求和响应日志;
  • CORS:支持跨域请求;
  • HTTPS:配置HTTPS证书;

好在Golang社区中已有多款成熟完善的HTTP框架。
Gin和Echo功能相似,但Echo文档更齐全性能更好,因此本文选择Echo作为HTTP框架,接下来详细介绍Echo的用法。

Echo教程

Echo封装的简约但不失灵活,只需以下代码就能快速实现一个高性能HTTP服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
import (
"net/http"

"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()
e.GET("/hello", func(context echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Start(":8080") // HTTP服务监听在8080端口
}

要实现需要响应JSON也非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   // 响应map类型JSON
e.GET("/map", func(context echo.Context) error {
return context.JSON(http.StatusOK, map[string]interface{}{"Hello": "World"})
})

// 响应数组类型JSON
e.GET("/array", func(context echo.Context) error {
return context.JSON(http.StatusOK, []string{"Hello", "World"})
})

// 响应结构体类型JSON
type Hi struct {
Hello string `json:"Hello"`
}
e.GET("/struct", func(context echo.Context) error {
return context.JSON(http.StatusOK, Hi{
Hello: "World",
})
})

Echo获取请求参数

如果请求中带有参数,Echo能方便的帮你解析出来:

1
2
3
4
5
   e.GET("/params/:operationName", func(context echo.Context) error {
email := c.QueryParam("email") // 从URL params?email=abc 中提取email字段的值
operationName := c.Param("operationName") // 从URL params/:abc 中提取operationName字段的值
variables := c.FormValue("variables") // 从POST Form请求的body中提取variables字段的值
})

Echo还提供更强大的Bind功能,能根据请求自动的提取结构化的参数,同时还能校验参数是否合法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   // 定义参数的结构
type Params struct {
Email string `validate:"required,email"` // 改字段必填,并且是email格式
// 从JSON和Form请求中提取的字段名称是operationName,从URL中提取的字段名称是operation_name
OperationName string `json:"operationName" form:"operationName" query:"operation_name"`
Variables map[string]interface{}
}
e.GET("/structParams", func(context echo.Context) (err error) {
params:= Params{}
// Bind将自动根据请求类型,从URL、Body中提取参数转换为Params struct中定义的结构
err = context.Bind(&params)
// 如果校验失败,err将非空表示校验失败信息
if err != nil {
retuen
}
})

Echo错误处理

在获取响应给客户端的数据时可能会发生异常,这时候需要HTTP服务作出响应,Echo的错误处理设计的很优雅:

1
2
3
4
5
6
7
8
9
10
   e.GET("/movie", func(context echo.Context) (err error) {
// 获取电影数据,可能发生错误
movie, err := getMovie()
// 如果获取电影失败,直接返回错误
if err != nil {
// 客户端将收到HTTP 500响应码,内容为:{"message": "err.Error()对应的字符串"}
retuen
}
return context.JSON(http.StatusOK, movie)
})

如果你不想返回默认的500错误,例如没有权限,可以自定义错误码:

1
2
3
4
5
6
7
   e.GET("/movie", func(context echo.Context) (err error) {
movie, err := getMovie()
if err != nil {
// 客户端将收到HTTP 401响应码,内容为:{"message": "err.Error()对应的字符串"}
retuen echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
})

如果你不想在出错时响应JSON,例如需要响应HTTP,可以自定义错误渲染逻辑:

1
2
3
e.HTTPErrorHandler = func(err error, context echo.Context) {
return context.HTML(http.StatusUnauthorized, err.Error())
}

Echo常用中间件

Echo内置了大量实用的中间件,例如:

1
2
3
4
5
6
7
8
9
10
11
12
import (
"github.com/labstack/echo/middleware"
)

// 采用Gzip压缩响应后能传输更少的字节,如果的HTTP服务没有在Nginx背后建议开启
e.Use(middleware.Gzip())

// 支持接口跨域请求
e.Use(middleware.CORS())

// 记录请求日志
e.Use(middleware.Logger())

总结

自从2009年发布Golang到现在,Golang社交已发展的非常成熟,你可以在开源社区找到几乎所有的现成框架。
使用Golang开发出的Graphql服务不仅能支撑高并发量,编译出的产物也非常小,
由于不依赖虚拟机,搭配上Docker带来的自动化部署给开发者节省成本的同时又带来稳定和便利。

虽然Golang能开发出小巧高效的Graphql服务,但可以看出在实现GraphQL取数逻辑那块有大量繁琐重复的工作,
这归咎于Golang语法太过死板无法给框架开发者发挥空间来实现使用更便利的框架,希望后续Golang2能提供更灵活的语法来优化这些不足。